package cn.com.zero.manager.gateway.strategy;

import cn.com.zero.manager.gateway.config.CustomProperty;
import cn.com.zero.manager.gateway.constants.GateWayConstant;
import cn.hutool.core.util.StrUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.DefaultResponse;
import org.springframework.cloud.client.loadbalancer.EmptyResponse;
import org.springframework.cloud.client.loadbalancer.Request;
import org.springframework.cloud.client.loadbalancer.Response;
import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.http.HttpHeaders;
import org.springframework.util.ObjectUtils;
import reactor.core.publisher.Mono;

import java.util.*;

/**
 * @author Yang Yao
 * @version 1.0.0
 * @Description 灰度发布负载均衡策略
 * @createTime 2023/3/2 16:10
 */
public class GrayLoadBalancer implements ReactorServiceInstanceLoadBalancer {


    private static final Logger log = LoggerFactory.getLogger(GrayLoadBalancer.class);

    private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
    private CustomProperty customProperty;
    private String serviceId;

    public GrayLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider, String serviceId, CustomProperty customProperty) {
        this.serviceId = serviceId;
        this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
        this.customProperty = customProperty;
    }


    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
        HttpHeaders headers = (HttpHeaders) request.getContext();
        if (this.serviceInstanceListSupplierProvider != null) {
            ServiceInstanceListSupplier supplier = this.serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);
            return supplier.get().next().map(item -> getInstanceResponse(item, headers));
        }
        return null;
    }

    private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances, HttpHeaders headers) {
        if (instances.isEmpty()) {
            return getServiceInstanceEmptyResponse();
        } else {
            return getServiceInstanceResponseByUidsOrGrayTag(instances, headers);
        }
    }


    /**
     * 从nacos获取服务实例列表，并根据策略返回灰度服务的实例还是正常服务的实例
     */
    private Response<ServiceInstance> getServiceInstanceResponseByUidsOrGrayTag(List<ServiceInstance> instances, HttpHeaders headers) {
        List<ServiceInstance> grayInstances = new ArrayList<>();
        List<ServiceInstance> normalInstances = new ArrayList<>();

        for (ServiceInstance instance : instances) {
            Map<String, String> metadata = instance.getMetadata();
            // nacos元数据包含“gray-tag”的key值,且value="version*"，则判定为灰度实例
            String isGrayInstance = metadata.get(GateWayConstant.GRAY_TAG);
            if (StrUtil.isBlank(isGrayInstance)) {
                normalInstances.add(instance);
            } else {
                if (StrUtil.contains(isGrayInstance, GateWayConstant.GRAY_VERSION)) {
                    grayInstances.add(instance);
                }
            }
        }
        //没有灰度服务，直接返回
        if (grayInstances.isEmpty()) {
            return new DefaultResponse(chooseOneInstance(normalInstances));
        }
        //有灰度服务，判断是否需要灰度
        if (checkIfNeedGray(headers)) {
            log.info("gray service of {} will be called", this.serviceId);
            return new DefaultResponse(chooseOneInstance(grayInstances, headers));
        }
        return new DefaultResponse(chooseOneInstance(normalInstances));
    }

    /**
     * 灰度判断逻辑：
     * 1. 判断请求header里是否用灰度标识的 kv，且请求的用户 id 是否在灰度用户池中，有则走灰度服务
     * <p>
     * 2. 1不满足，走正常服务
     */
    private boolean checkIfNeedGray(HttpHeaders headers) {
        // 获取请求头gray-tag
        String grayTag = headers.getFirst(GateWayConstant.GRAY_TAG);
        String uid = headers.getFirst(GateWayConstant.UID);
        if (!StrUtil.isBlank(grayTag)) {
            if (StrUtil.contains(grayTag, GateWayConstant.GRAY_VERSION) && !StrUtil.isBlank(uid) && customProperty.getUserIds().contains(uid)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 获取灰度服务
     * 从实例列表中获取其中一个实例的策略实现，这里采用的是版本号精确匹配
     * pick strategy 可以根据业务需要，在这个方法里改写
     */
    private ServiceInstance chooseOneInstance(List<ServiceInstance> serviceInstances, HttpHeaders headers) {
        // 根据版本号精确匹配灰度服务
        String version = headers.getFirst(GateWayConstant.GRAY_TAG);
        for (ServiceInstance serviceInstance : serviceInstances) {
            Map<String, String> metadata = serviceInstance.getMetadata();
            // 获取nacos元数据包含“gray-tag”的key值
            String isGrayInstance = metadata.get(GateWayConstant.GRAY_TAG);
            if (isGrayInstance.equalsIgnoreCase(version)) {
                return serviceInstance;
            }
        }
        return null;
    }

    /**
     * 从实例列表中获取其中一个实例的策略实现，这里采用的是随机挑选
     * pick strategy 可以根据业务需要，在这个方法里改写
     * 可根据权重实现选择服务
     */
    private ServiceInstance chooseOneInstance(List<ServiceInstance> serviceInstances) {
        // key-当前服务的ip:端口，value-权重值
        Map<String, Double> weightMap = new HashMap<>();
        Map<String, ServiceInstance> instanceMap = new HashMap<>();
        for (ServiceInstance serviceInstance : serviceInstances) {
            String host = serviceInstance.getHost();
            int port = serviceInstance.getPort();
            // ip和端口组合成键
            String key = host + ":" + port;
            Map<String, String> metadata = serviceInstance.getMetadata();
            String weight = metadata.get(GateWayConstant.NACOS_WEIGHT);
            weightMap.put(key, Double.valueOf(weight));
            instanceMap.put(key, serviceInstance);
        }
        String key = getServerByWeight(weightMap);
        return instanceMap.get(key);
    }

    private Response<ServiceInstance> getServiceInstanceEmptyResponse() {
        log.warn("No servers available for service: " + this.serviceId);
        return new EmptyResponse();
    }

    /**
     * <h3>根据权重获取对应服务，传入服务：权重map</h3>
     *
     * @param map 服务：权重map
     * @return 返回选择的服务的key
     **/
    private  String getServerByWeight(Map<String, Double> map) {
        if (map.isEmpty()) {
            return null;
        }
        // 服务权重总值
        double total = 0.0;
        // 权重最小值
        Collection<Double> values = map.values();
        Double[] doubles = values.toArray(new Double[0]);
        Arrays.sort(doubles);
        double min = doubles[0];

        // 计算所有权重
        for (Double value : values) {
            total += value;
        }
        Random random = new Random();
        // 在权重范围内随机
        double nextDouble = min + (random.nextDouble() * (total - min));
        nextDouble = (double) Math.round(nextDouble * 100) / 100;
        // 遍历所有服务提供者provide的ip地址
        for (String key : map.keySet()) {
            // 取出权重值
            Double weight = map.get(key);
            // 权重在范围内，则返回对应的服务
            if (nextDouble < weight) {
                return key;
            }
            // 否则减去权重，继续下一次循环，匹配对应的key
            nextDouble -= weight;
        }
        return null;
    }
}
